package main.graph;

import y.base.EdgeMap;
import y.base.Graph;
import y.base.Edge;
import y.base.NodeMap;
import y.base.Node;
import y.base.EdgeCursor;
import y.base.NodeCursor;

import main.model.*;

import java.util.*;

/**
 * This class represents a graph whose nodes and edges are associated with values
 */
public class ValuedGraph extends Graph
{
  /**
   * internal NodeMap that stores additional node data
   */
  private NodeMap nodeValues;
  
  /**
   * internal EdgeMap that stores additional edge data
   */
  private EdgeMap edgeValues;
  
  private String name = null; //the identity string of the graph
  
  //hold the map from a string to the set of elements (node or edge) in the graph
  HashMap<String, ArrayList<Object>> label2elements = new HashMap<String, ArrayList<Object>>();
 
  /////////////////////////////////////////////////////////////////////////////
  // CONSTRUCTION /////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////
  
  /** Creates a new instance of ExtendedGraph */
  public ValuedGraph()
  {
    nodeValues = createNodeMap();
    edgeValues = createEdgeMap();
    
    label2elements = new HashMap<String, ArrayList<Object>>();
  }
  
  /** Creates a graph of type ExtendedGraph */
  public Graph createGraph()
  {
    return new ValuedGraph();
  }
  
  /**
   * make a copy of this graph with all associated values
   * @return
   */
  public ValuedGraph makeCopy(){
	  ValuedGraph ans = new ValuedGraph();
	  
	  //from original nodes to new nodes
	  HashMap<Node, Node> origToNew = new HashMap<Node, Node>();
	  
	  for(NodeCursor nc = nodes(); nc.ok(); nc.next()){
		  Node origNode = nc.node();
		  Node newNode = ans.createNode();
		  origToNew.put(origNode, newNode);
		  ans.putNodeValue(newNode, getNodeValue(origNode));
	  }
	  
	  //from original edges to new edges
	  HashMap<Edge, Edge> origEdgeToNewEdge = new HashMap<Edge, Edge>();
	  
	  for(EdgeCursor ec = edges(); ec.ok(); ec.next()){
		 Edge origEdge = ec.edge();
		 Node origSource = origEdge.source();
		 Node origTarget = origEdge.target();
		 Node source = origToNew.get(origSource);
		 Node target = origToNew.get(origTarget);
		 Edge edge = ans.createEdge(source, target);
		 ans.putEdgeValue(edge, getEdgeValue(origEdge));
		 
		 origEdgeToNewEdge.put(origEdge, edge);
	  }
	  
	  
	  Iterator<String> it = label2elements.keySet().iterator();
	  while(it.hasNext()){
		  String key = it.next();
		  
		  ArrayList origEles = label2elements.get(key);
		  
		  //a new list of elements
		  ArrayList eles = new ArrayList();
		  
		  for(int i = 0; i < origEles.size(); i++){
			  Object origEle = origEles.get(i);
			  
			  if(origEle instanceof Node){
				  Node ele = origToNew.get(origEle);
				  eles.add(ele);
			  }
			  else if(origEle instanceof Edge){
				  Edge ele = origEdgeToNewEdge.get(origEle);
				  eles.add(ele);
			  }
		  }
		  
		  ans.putLabel2Elements(key, eles);
	  }
	  
	  
	  return ans;
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // SETTER AND GETTER ////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////
  
  /**
   * set the name
   */
  public void setName(String n){
	  name = n;
  }
  
  public String getName(){
	  return name;
  }
  
  /**
   * Returns the edge weight associated with the given edge.
   * By default null will be returned.
   */
  public Object getEdgeValue(Edge e)
  {
    return edgeValues.get(e);
  }
  
  /**
   * Sets the edge weight associated with the given edge.
   */
  public void putEdgeValue(Edge e, Object value)
  { 
    edgeValues.set(e, value);
  }
  
  public Object getNodeValue(Node n){
	  return nodeValues.get(n);
  }
  
  public void putNodeValue(Node n, Object value)
  {
    nodeValues.set(n, value);
  }
  
  public Node getNodeByEntityLabel(String label){
	  Node ans = null;
	  
	  for(NodeCursor nc = nodes(); nc.ok(); nc.next()){
		  Node node = nc.node();
		  Object v = getNodeValue(node);
		  if(v instanceof Entity){
			  Entity entity = (Entity) v;
			  if(entity.getLabel().equals(label)){
				  ans = node;
				  break;
			  }
		  }
	  }
	  
	  return ans;
  }
  
  public Node getNodeCorrespondingEntity(Entity entity){
	  Node ans = null;
	  
	  for(NodeCursor nc=nodes(); nc.ok(); nc.next()){
		  Node node = nc.node();
		  Object v = getNodeValue(node);
		  if(v instanceof Entity){
			  Entity ent = (Entity) v;
			  if(ent == entity){
				  ans = node;
				  break;
			  }
		  }
	  }
	  
	  
	  return ans;
  }
  
  public boolean hasNodeValue(Entity entity){
	  boolean ans = false;
	  
	  for(NodeCursor nc=nodes(); nc.ok(); nc.next()){
		  Node node = nc.node();
		  Object v = getNodeValue(node);
		  if(v instanceof Entity){
			  Entity ent = (Entity) v;
			  if(ent == entity){
				  ans = true;
				  break;
			  }
		  }
	  }
	  
	  
	  return ans;
  }
  
  public boolean hasEdge(Entity sourceEnt, Entity targetEnt, Relationship relationship){
	  boolean ans = false;
	  
	  Node source = getNodeCorrespondingEntity(sourceEnt);
	  if(source == null)
		  return ans;
	  Node target = getNodeCorrespondingEntity(targetEnt);
	  if(target == null)
		  return ans;
	  
	  for(EdgeCursor ec = source.outEdges(); ec.ok(); ec.next()){
		  Edge edge = ec.edge();
		  Object v = getEdgeValue(edge);
		  if(v instanceof Relationship){
			  Relationship rel = (Relationship) v;
			  if(rel == relationship){
				  ans = true;
				  break;
			  }
		  }
	  }
	  
	  return ans;
  }
  
  public void putLabel2Elements(String label, ArrayList eles){
	  label2elements.put(label, eles);
  }
  
  public ArrayList getElementsByLabel(String label){
	  return label2elements.get(label);
  }
  
  public void putLabel2Element(String label, Object ele){
	  
	  ArrayList eles = label2elements.get(label);
	  
	  if(eles == null){
		  ArrayList holder = new ArrayList();
		  holder.add(ele);
		  label2elements.put(label, holder);
	  }
	  else
		  eles.add(ele);
	  
  }
  
  public static void main(String[] args)
  {
	 ValuedGraph graph = new ValuedGraph();
	 Node n1 = graph.createNode();
	 graph.putNodeValue(n1, "a");
	 Node n2 = graph.createNode();
	 graph.putNodeValue(n2, "b");
	 Node n3 = graph.createNode();
	 graph.putNodeValue(n3, "c");
	 Node n4 = graph.createNode();
	 graph.putNodeValue(n4, "d");
	 
	 Edge e1 = graph.createEdge(n1, n2);
	 graph.putEdgeValue(e1, "a-b");
	 Edge e2 = graph.createEdge(n2, n3);
	 graph.putEdgeValue(e2, "b-c");
	 Edge e3 = graph.createEdge(n3, n4);
	 graph.putEdgeValue(e3, "c-d");
	 Edge e4 = graph.createEdge(n4, n1);
	 graph.putEdgeValue(e4, "d-a");
	 
     
     
     for(NodeCursor nc = graph.nodes(); nc.ok(); nc.next())
     {
    	 Node node = nc.node();
    	 System.out.println("A node: " + graph.getNodeValue(node));
    	 System.out.println("Node index: " + node.index());
     }
     for(EdgeCursor ec = graph.edges(); ec.ok(); ec.next())
     {
    	 Edge edge = ec.edge();
    	 System.out.println("An edge: " + graph.getEdgeValue(ec.edge()));
    	 System.out.println("Edge index: " + edge.index());
     }
     
     System.out.println(graph);
  }
}
